/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) MoreStrings.java 2018-08-05 18:23
 */

package cn.jh.common.core.base;

import com.google.common.base.CharMatcher;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.math.BigDecimal;
import java.nio.charset.Charset;

import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.ISO_8859_1;


/**
 * Helper functions that operate on any {@code String}, and are not already provided in
 * {@link com.google.common.base.Strings}.
 *
 * @author Fuchun
 * @since 1.0
 */
public class MoreStrings {

    /**
     * The GBK Charset constant (GB18030 &gt; GBK &gt; GB2312).
     */
    public static final Charset GBK;

    private static final String[] EMPTY_STRINGS = new String[0];

    static {
        if (Charset.isSupported("GB18030")) {
            GBK = Charset.forName("GB18030");
        } else if (Charset.isSupported("GBK")) {
            GBK = Charset.forName("GBK");
        } else if (Charset.isSupported("GB2312")) {
            GBK = Charset.forName("GB2312");
        } else {
            GBK = ISO_8859_1;
        }
    }

    /**
     * Return an empty {@code String[]} object.
     */
    @Contract(pure = true)
    public static String[] empty() {
        return EMPTY_STRINGS;
    }

    @Contract(pure = true)
    @NotNull
    public static String[] toArray(String first, String ... strings) {
        if (strings == null || strings.length == 0) {
            return new String[] { first };
        }
        String[] result = new String[1 + strings.length];
        result[0] = first;
        int i = 1;
        for (String str : strings) {
            result[i++] = str;
        }
        return result;
    }

    /**
     * Removes control characters (char &lt;= 32) from both
     * ends of this String returning {@code null} if the String is
     * empty ("") after the trim or if it is {@code null}.
     * <p />
     * The String is trimmed using {@link String#trim()}.
     * Trim removes start and end characters &lt;= 32.
     * <pre>{@code
     * MoreStrings.trimToNull("")        = null
     * MoreStrings.trimToNull(null)      = null
     * MoreStrings.trimToNull("  ")      = null
     * MoreStrings.trimToNull("abc")     = "abc"
     * MoreStrings.trimToNull(" ab c  ") = "ab c"
     * }</pre>
     *
     * @param input the String to be trimmed, may be null
     * @return the trimmed String,
     *  {@code null} if only chars &lt;= 32, empty or null String input
     */
    @Nullable
    public static String trimToNull(String input) {
        String str = trim(input);
        return str == null ? null : (str.isEmpty() ? null : str);
    }

    /**
     * Removes control characters (char &lt;= 32) from both
     * ends of this String returning an empty String ("") if the String
     * is empty ("") after the trim or if it is {@code null}.
     * <p/>
     * The String is trimmed using {@link String#trim()}.
     * Trim removes start and end characters &lt;= 32.
     * <pre>{@code
     * MoreStrings.trimToEmpty("")        = ""
     * MoreStrings.trimToEmpty(null)      = ""
     * MoreStrings.trimToEmpty("  ")      = ""
     * MoreStrings.trimToEmpty("abc")     = "abc"
     * MoreStrings.trimToEmpty(" ab c  ") = "ab c"
     * }</pre>
     *
     * @param input the String to be trimmed, may be null
     * @return the trimmed String, or an empty String if {@code null} input
     */
    @NotNull
    @Contract(pure = true)
    public static String trimToEmpty(String input) {
        return input == null ? "" : input.trim();
    }

    /**
     * Removes control characters (char &lt;= 32) from both
     * ends of this String, handling {@code null} by returning
     * {@code null}.
     * <p/>
     * The String is trimmed using {@link String#trim()}.
     * Trim removes start and end characters &lt;= 32.
     * <pre>{@code
     * MoreStrings.trim(null)        = null
     * MoreStrings.trim("")          = ""
     * MoreStrings.trim("    ")      = ""
     * MoreStrings.trim("abc")       = "abc"
     * MoreStrings.trim(" ab c ")    = "ab c"
     * }</pre>
     *
     * @param input the String to be trimmed, may be null
     * @return the trimmed string, {@code null} if null String input
     */
    @Contract("null -> null")
    public static String trim(String input) {
        return input == null ? null : input.trim();
    }

    /**
     * Trim leading and trailing whitespace from the given {@code String}.
     * For example: <pre>{@code
     * MoreStrings.trimWhitespace(null)          = null
     * MoreStrings.trimWhitespace("")            = ""
     * MoreStrings.trimWhitespace("    ")        = ""
     * MoreStrings.trimWhitespace("\u202F  ")    = ""
     * "\u202F  ".trim()                         = "\u202F"
     * }</pre>
     * Determines whether a character is whitespace according to the latest Unicode standard, as
     * illustrated <a href="http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5Cp%7Bwhitespace%7D">here</a>.
     * This is not the same definition used by other Java APIs. (See a <a
     * href="http://spreadsheets.google.com/pub?key=pd8dAQyHbdewRsnE5x5GzKQ">comparison of several
     * definitions of "whitespace"</a>.)
     *
     * @param input the {@code String} to check
     * @return the trimmed {@code String}
     */
    @Contract("null -> null")
    public static String trimWhitespace(String input) {
        if (input == null || input.isEmpty()) return input;
        return CharMatcher.whitespace().trimFrom(input);
    }

    /**
     * Trim leading and trailing breaking whitespace from the given {@code String}.
     * For example: <pre>{@code
     * MoreStrings.trimBreakingWhitespace(null)       = null
     * MoreStrings.trimBreakingWhitespace("")         = ""
     * MoreStrings.trimBreakingWhitespace("    ")     = ""
     * MoreStrings.trimBreakingWhitespace("\u2009  ") = ""
     * MoreStrings.trimBreakingWhitespace("\u202F  ") = "\u202F"
     * }</pre>
     *
     * @param input the {@code String} to check
     * @return the trimmed {@code String}
     */
    @Contract("null -> null")
    public static String trimBreakingWhitespace(String input) {
        if (input == null || input.isEmpty()) return input;
        return CharMatcher.breakingWhitespace().trimFrom(input);
    }

    @Contract("null, _ -> null")
    public static String transcode(String input, Charset target) {
        if (input == null || input.isEmpty()) return input;
        return new String(input.getBytes(target), target);
    }

    @Contract("null, _ -> null")
    public static String transcode(byte[] input, Charset target) {
        if (input == null) return null;
        checkArgument(target != null, "The target Charset must be not null");
        return new String(input, target);
    }

    @Contract("null, _, _ -> null")
    public static String transcode(byte[] input, Charset inputCharset, Charset target) {
        if (input == null) return null;
        if (input.length == 0) return "";
        checkArgument(inputCharset != null, "The input Charset must be not null");
        String inputStr = new String(input, inputCharset);
        return transcode(inputStr, target);
    }

    @Contract("null, _ -> param2")
    public static Short toShort(String input, Short defaultValue) {
        Integer value = toInteger(input, defaultValue != null ? defaultValue.intValue() : null);
        return value != null ? value.shortValue() : null;
    }

    @Contract("null, _ -> param2")
    public static Integer toInteger(String input, Integer defaultValue) {
        String str;
        if (input == null || (str = input.trim()).isEmpty()) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException ex) {
            return defaultValue;
        }
    }

    @Contract("null, _ -> param2")
    public static Long toLong(String input, Long defaultValue) {
        String str;
        if (input == null || (str = input.trim()).isEmpty()) {
            return defaultValue;
        }
        try {
            return Long.parseLong(str);
        } catch (NumberFormatException ex) {
            return defaultValue;
        }
    }

    @Contract("null, _ -> param2")
    public static BigDecimal toDecimal(String input, BigDecimal defaultValue) {
        String str;
        if (input == null || (str = input.trim()).isEmpty()) {
            return defaultValue;
        }
        try {
            return new BigDecimal(str);
        } catch (NumberFormatException ex) {
            return defaultValue;
        }
    }

    private MoreStrings() {
    }
}
